/*!
 * ECharts, a javascript interactive chart library.
 *
 * Copyright (c) 2015, Baidu Inc.
 * All rights reserved.
 *
 * LICENSE
 * https://github.com/ecomfe/echarts/blob/master/LICENSE.txt
 */
/**
 * echarts
 *
 * @desc echarts基于Canvas，纯Javascript图表库，提供直观，生动，可交互，可个性化定制的数据统计图表。
 * @author Kener (@Kener-林峰, kener.linfeng@gmail.com)
 *
 */


var ecConfig = require('./config');
var zrUtil = require('zrender/tool/util');
var zrEvent = require('zrender/tool/event');
var self = {};
var _canvasSupported = require('zrender/tool/env').canvasSupported;
var _idBase = new Date() - 0;
var _instances = {};
// ECharts实例map索引
var DOM_ATTRIBUTE_KEY = '_echarts_instance_';
self.version = '2.2.7';
self.dependencies = { zrender: '2.1.1' };
/**
     * 入口方法
     */
self.init = function (dom, theme) {
    var zrender = require('zrender');
    if (zrender.version.replace('.', '') - 0 < self.dependencies.zrender.replace('.', '') - 0) {
        console.error('ZRender ' + zrender.version + ' is too old for ECharts ' + self.version + '. Current version need ZRender ' + self.dependencies.zrender + '+');
    }
    dom = dom instanceof Array ? dom[0] : dom;
    // dom与echarts实例映射索引
    var key = dom.getAttribute(DOM_ATTRIBUTE_KEY);
    if (!key) {
        key = _idBase++;
        dom.setAttribute(DOM_ATTRIBUTE_KEY, key);
    }
    if (_instances[key]) {
        // 同一个dom上多次init，自动释放已有实例
        _instances[key].dispose();
    }
    _instances[key] = new Echarts(dom);
    _instances[key].id = key;
    _instances[key].canvasSupported = _canvasSupported;
    _instances[key].setTheme(theme);
    return _instances[key];
};
/**
     * 通过id获得ECharts实例，id可在实例化后读取
     */
self.getInstanceById = function (key) {
    return _instances[key];
};
/**
     * 消息中心
     */
function MessageCenter() {
    zrEvent.Dispatcher.call(this);
}
zrUtil.merge(MessageCenter.prototype, zrEvent.Dispatcher.prototype, true);
/**
     * 基于zrender实现Echarts接口层
     * @param {HtmlElement} dom 必要
     */
function Echarts(dom) {
    // Fxxk IE11 for breaking initialization without a warrant;
    // Just set something to let it be!
    // by kener 2015-01-09
    dom.innerHTML = '';
    this._themeConfig = {};
    // zrUtil.clone(ecConfig);
    this.dom = dom;
    // this._zr;
    // this._option;                    // curOption clone
    // this._optionRestore;             // for restore;
    // this._island;
    // this._toolbox;
    // this._timeline;
    // this._refreshInside;             // 内部刷新标志位
    this._connected = false;
    this._status = {
        // 用于图表间通信
        dragIn: false,
        dragOut: false,
        needRefresh: false
    };
    this._curEventType = false;
    // 破循环信号灯
    this._chartList = [];
    // 图表实例
    this._messageCenter = new MessageCenter();
    this._messageCenterOutSide = new MessageCenter();
    // Echarts层的外部消息中心，做Echarts层的消息转发
    // resize方法经常被绑定到window.resize上，闭包一个this
    this.resize = this.resize();
    // 初始化::构造函数
    this._init();
}
/**
     * ZRender EVENT
     *
     * @inner
     * @const
     * @type {Object}
     */
var ZR_EVENT = require('zrender/config').EVENT;
/**
     * 要绑定监听的zrender事件列表
     *
     * @const
     * @inner
     * @type {Array}
     */
var ZR_EVENT_LISTENS = [
    'CLICK',
    'DBLCLICK',
    'MOUSEOVER',
    'MOUSEOUT',
    'DRAGSTART',
    'DRAGEND',
    'DRAGENTER',
    'DRAGOVER',
    'DRAGLEAVE',
    'DROP'
];
/**
     * 对echarts的实例中的chartList属性成员，逐个进行方法调用，遍历顺序为逆序
     * 由于在事件触发的默认行为处理中，多次用到相同逻辑，所以抽象了该方法
     * 由于所有的调用场景里，最多只有两个参数，基于性能和体积考虑，这里就不使用call或者apply了
     *
     * @inner
     * @param {ECharts} ecInstance ECharts实例
     * @param {string} methodName 要调用的方法名
     * @param {*} arg0 调用参数1
     * @param {*} arg1 调用参数2
     * @param {*} arg2 调用参数3
     */
function callChartListMethodReverse(ecInstance, methodName, arg0, arg1, arg2) {
    var chartList = ecInstance._chartList;
    var len = chartList.length;
    while (len--) {
        var chart = chartList[len];
        if (typeof chart[methodName] === 'function') {
            chart[methodName](arg0, arg1, arg2);
        }
    }
}
Echarts.prototype = {
    /**
         * 初始化::构造函数
         */
    _init: function () {
        var self = this;
        var _zr = require('zrender').init(this.dom);
        this._zr = _zr;
        // wrap: n,e,d,t for name event data this
        this._messageCenter.dispatch = function (type, event, eventPackage, that) {
            eventPackage = eventPackage || {};
            eventPackage.type = type;
            eventPackage.event = event;
            self._messageCenter.dispatchWithContext(type, eventPackage, that);
            self._messageCenterOutSide.dispatchWithContext(type, eventPackage, that);    // 如下注掉的代码，@see: https://github.com/ecomfe/echarts-discuss/issues/3
                                                                                         // if (type != 'HOVER' && type != 'MOUSEOUT') {    // 频繁事件直接抛出
                                                                                         //     setTimeout(function(){
                                                                                         //         self._messageCenterOutSide.dispatchWithContext(
                                                                                         //             type, eventPackage, that
                                                                                         //         );
                                                                                         //     },50);
                                                                                         // }
                                                                                         // else {
                                                                                         //     self._messageCenterOutSide.dispatchWithContext(
                                                                                         //         type, eventPackage, that
                                                                                         //     );
                                                                                         // }
        };
        this._onevent = function (param) {
            return self.__onevent(param);
        };
        for (var e in ecConfig.EVENT) {
            if (e != 'CLICK' && e != 'DBLCLICK' && e != 'HOVER' && e != 'MOUSEOUT' && e != 'MAP_ROAM') {
                this._messageCenter.bind(ecConfig.EVENT[e], this._onevent, this);
            }
        }
        var eventBehaviors = {};
        this._onzrevent = function (param) {
            return self[eventBehaviors[param.type]](param);
        };
        // 挂载关心的事件
        for (var i = 0, len = ZR_EVENT_LISTENS.length; i < len; i++) {
            var eventName = ZR_EVENT_LISTENS[i];
            var eventValue = ZR_EVENT[eventName];
            eventBehaviors[eventValue] = '_on' + eventName.toLowerCase();
            _zr.on(eventValue, this._onzrevent);
        }
        this.chart = {};
        // 图表索引
        this.component = {};
        // 组件索引
        // 内置图表
        // 孤岛
        var Island = require('./chart/island');
        this._island = new Island(this._themeConfig, this._messageCenter, _zr, {}, this);
        this.chart.island = this._island;
        // 内置通用组件
        // 工具箱
        var Toolbox = require('./component/toolbox');
        this._toolbox = new Toolbox(this._themeConfig, this._messageCenter, _zr, {}, this);
        this.component.toolbox = this._toolbox;
        var componentLibrary = require('./component');
        componentLibrary.define('title', require('./component/title'));
        componentLibrary.define('tooltip', require('./component/tooltip'));
        componentLibrary.define('legend', require('./component/legend'));
        if (_zr.getWidth() === 0 || _zr.getHeight() === 0) {
            console.error('Dom\u2019s width & height should be ready before init.');
        }
    },
    /**
         * ECharts事件处理中心
         */
    __onevent: function (param) {
        param.__echartsId = param.__echartsId || this.id;
        // 来自其他联动图表的事件
        var fromMyself = param.__echartsId === this.id;
        if (!this._curEventType) {
            this._curEventType = param.type;
        }
        switch (param.type) {
        case ecConfig.EVENT.LEGEND_SELECTED:
            this._onlegendSelected(param);
            break;
        case ecConfig.EVENT.DATA_ZOOM:
            if (!fromMyself) {
                var dz = this.component.dataZoom;
                if (dz) {
                    dz.silence(true);
                    dz.absoluteZoom(param.zoom);
                    dz.silence(false);
                }
            }
            this._ondataZoom(param);
            break;
        case ecConfig.EVENT.DATA_RANGE:
            fromMyself && this._ondataRange(param);
            break;
        case ecConfig.EVENT.MAGIC_TYPE_CHANGED:
            if (!fromMyself) {
                var tb = this.component.toolbox;
                if (tb) {
                    tb.silence(true);
                    tb.setMagicType(param.magicType);
                    tb.silence(false);
                }
            }
            this._onmagicTypeChanged(param);
            break;
        case ecConfig.EVENT.DATA_VIEW_CHANGED:
            fromMyself && this._ondataViewChanged(param);
            break;
        case ecConfig.EVENT.TOOLTIP_HOVER:
            fromMyself && this._tooltipHover(param);
            break;
        case ecConfig.EVENT.RESTORE:
            this._onrestore();
            break;
        case ecConfig.EVENT.REFRESH:
            fromMyself && this._onrefresh(param);
            break;
        // 鼠标同步
        case ecConfig.EVENT.TOOLTIP_IN_GRID:
        case ecConfig.EVENT.TOOLTIP_OUT_GRID:
            if (!fromMyself) {
                // 只处理来自外部的鼠标同步
                var grid = this.component.grid;
                if (grid) {
                    this._zr.trigger('mousemove', {
                        connectTrigger: true,
                        zrenderX: grid.getX() + param.x * grid.getWidth(),
                        zrenderY: grid.getY() + param.y * grid.getHeight()
                    });
                }
            } else if (this._connected) {
                // 来自自己，并且存在多图联动，空间坐标映射修改参数分发
                var grid = this.component.grid;
                if (grid) {
                    param.x = (param.event.zrenderX - grid.getX()) / grid.getWidth();
                    param.y = (param.event.zrenderY - grid.getY()) / grid.getHeight();
                }
            }
            break;    /*
                case ecConfig.EVENT.RESIZE :
                case ecConfig.EVENT.DATA_CHANGED :
                case ecConfig.EVENT.PIE_SELECTED :
                case ecConfig.EVENT.MAP_SELECTED :
                    break;
                */
        }
        // 多图联动，只做自己的一级事件分发，避免级联事件循环
        if (this._connected && fromMyself && this._curEventType === param.type) {
            for (var c in this._connected) {
                this._connected[c].connectedEventHandler(param);
            }
            // 分发完毕后复位
            this._curEventType = null;
        }
        if (!fromMyself || !this._connected && fromMyself) {
            // 处理了完联动事件复位
            this._curEventType = null;
        }
    },
    /**
         * 点击事件，响应zrender事件，包装后分发到Echarts层
         */
    _onclick: function (param) {
        callChartListMethodReverse(this, 'onclick', param);
        if (param.target) {
            var ecData = this._eventPackage(param.target);
            if (ecData && ecData.seriesIndex != null) {
                this._messageCenter.dispatch(ecConfig.EVENT.CLICK, param.event, ecData, this);
            }
        }
    },
    /**
         * 双击事件，响应zrender事件，包装后分发到Echarts层
         */
    _ondblclick: function (param) {
        callChartListMethodReverse(this, 'ondblclick', param);
        if (param.target) {
            var ecData = this._eventPackage(param.target);
            if (ecData && ecData.seriesIndex != null) {
                this._messageCenter.dispatch(ecConfig.EVENT.DBLCLICK, param.event, ecData, this);
            }
        }
    },
    /**
         * 鼠标移入事件，响应zrender事件，包装后分发到Echarts层
         */
    _onmouseover: function (param) {
        if (param.target) {
            var ecData = this._eventPackage(param.target);
            if (ecData && ecData.seriesIndex != null) {
                this._messageCenter.dispatch(ecConfig.EVENT.HOVER, param.event, ecData, this);
            }
        }
    },
    /**
         * 鼠标移出事件，响应zrender事件，包装后分发到Echarts层
         */
    _onmouseout: function (param) {
        if (param.target) {
            var ecData = this._eventPackage(param.target);
            if (ecData && ecData.seriesIndex != null) {
                this._messageCenter.dispatch(ecConfig.EVENT.MOUSEOUT, param.event, ecData, this);
            }
        }
    },
    /**
         * dragstart回调，可计算特性实现
         */
    _ondragstart: function (param) {
        // 复位用于图表间通信拖拽标识
        this._status = {
            dragIn: false,
            dragOut: false,
            needRefresh: false
        };
        callChartListMethodReverse(this, 'ondragstart', param);
    },
    /**
         * dragging回调，可计算特性实现
         */
    _ondragenter: function (param) {
        callChartListMethodReverse(this, 'ondragenter', param);
    },
    /**
         * dragstart回调，可计算特性实现
         */
    _ondragover: function (param) {
        callChartListMethodReverse(this, 'ondragover', param);
    },
    /**
         * dragstart回调，可计算特性实现
         */
    _ondragleave: function (param) {
        callChartListMethodReverse(this, 'ondragleave', param);
    },
    /**
         * dragstart回调，可计算特性实现
         */
    _ondrop: function (param) {
        callChartListMethodReverse(this, 'ondrop', param, this._status);
        this._island.ondrop(param, this._status);
    },
    /**
         * dragdone回调 ，可计算特性实现
         */
    _ondragend: function (param) {
        callChartListMethodReverse(this, 'ondragend', param, this._status);
        this._timeline && this._timeline.ondragend(param, this._status);
        this._island.ondragend(param, this._status);
        // 发生过重计算
        if (this._status.needRefresh) {
            this._syncBackupData(this._option);
            var messageCenter = this._messageCenter;
            messageCenter.dispatch(ecConfig.EVENT.DATA_CHANGED, param.event, this._eventPackage(param.target), this);
            messageCenter.dispatch(ecConfig.EVENT.REFRESH, null, null, this);
        }
    },
    /**
         * 图例选择响应
         */
    _onlegendSelected: function (param) {
        // 用于图表间通信
        this._status.needRefresh = false;
        callChartListMethodReverse(this, 'onlegendSelected', param, this._status);
        if (this._status.needRefresh) {
            this._messageCenter.dispatch(ecConfig.EVENT.REFRESH, null, null, this);
        }
    },
    /**
         * 数据区域缩放响应
         */
    _ondataZoom: function (param) {
        // 用于图表间通信
        this._status.needRefresh = false;
        callChartListMethodReverse(this, 'ondataZoom', param, this._status);
        if (this._status.needRefresh) {
            this._messageCenter.dispatch(ecConfig.EVENT.REFRESH, null, null, this);
        }
    },
    /**
         * 值域漫游响应
         */
    _ondataRange: function (param) {
        this._clearEffect();
        // 用于图表间通信
        this._status.needRefresh = false;
        callChartListMethodReverse(this, 'ondataRange', param, this._status);
        // 没有相互影响，直接刷新即可
        if (this._status.needRefresh) {
            this._zr.refreshNextFrame();
        }
    },
    /**
         * 动态类型切换响应
         */
    _onmagicTypeChanged: function () {
        this._clearEffect();
        this._render(this._toolbox.getMagicOption());
    },
    /**
         * 数据视图修改响应
         */
    _ondataViewChanged: function (param) {
        this._syncBackupData(param.option);
        this._messageCenter.dispatch(ecConfig.EVENT.DATA_CHANGED, null, param, this);
        this._messageCenter.dispatch(ecConfig.EVENT.REFRESH, null, null, this);
    },
    /**
         * tooltip与图表间通信
         */
    _tooltipHover: function (param) {
        var tipShape = [];
        callChartListMethodReverse(this, 'ontooltipHover', param, tipShape);
    },
    /**
         * 还原
         */
    _onrestore: function () {
        this.restore();
    },
    /**
         * 刷新
         */
    _onrefresh: function (param) {
        this._refreshInside = true;
        this.refresh(param);
        this._refreshInside = false;
    },
    /**
         * 数据修改后的反向同步dataZoom持有的备份数据
         */
    _syncBackupData: function (curOption) {
        this.component.dataZoom && this.component.dataZoom.syncBackupData(curOption);
    },
    /**
         * 打包Echarts层的事件附件
         */
    _eventPackage: function (target) {
        if (target) {
            var ecData = require('./util/ecData');
            var seriesIndex = ecData.get(target, 'seriesIndex');
            var dataIndex = ecData.get(target, 'dataIndex');
            dataIndex = seriesIndex != -1 && this.component.dataZoom ? this.component.dataZoom.getRealDataIndex(seriesIndex, dataIndex) : dataIndex;
            return {
                seriesIndex: seriesIndex,
                seriesName: (ecData.get(target, 'series') || {}).name,
                dataIndex: dataIndex,
                data: ecData.get(target, 'data'),
                name: ecData.get(target, 'name'),
                value: ecData.get(target, 'value'),
                special: ecData.get(target, 'special')
            };
        }
        return;
    },
    _noDataCheck: function (magicOption) {
        var series = magicOption.series;
        for (var i = 0, l = series.length; i < l; i++) {
            if (series[i].type == ecConfig.CHART_TYPE_MAP || series[i].data && series[i].data.length > 0 || series[i].markPoint && series[i].markPoint.data && series[i].markPoint.data.length > 0 || series[i].markLine && series[i].markLine.data && series[i].markLine.data.length > 0 || series[i].nodes && series[i].nodes.length > 0 || series[i].links && series[i].links.length > 0 || series[i].matrix && series[i].matrix.length > 0 || series[i].eventList && series[i].eventList.length > 0) {
                return false;    // 存在任意数据则为非空数据
            }
        }
        var loadOption = this._option && this._option.noDataLoadingOption || this._themeConfig.noDataLoadingOption || ecConfig.noDataLoadingOption || {
            text: this._option && this._option.noDataText || this._themeConfig.noDataText || ecConfig.noDataText,
            effect: this._option && this._option.noDataEffect || this._themeConfig.noDataEffect || ecConfig.noDataEffect
        };
        // 空数据
        this.clear();
        this.showLoading(loadOption);
        return true;
    },
    /**
         * 图表渲染
         */
    _render: function (magicOption) {
        this._mergeGlobalConifg(magicOption);
        if (this._noDataCheck(magicOption)) {
            return;
        }
        var bgColor = magicOption.backgroundColor;
        if (bgColor) {
            if (!_canvasSupported && bgColor.indexOf('rgba') != -1) {
                // IE6~8对RGBA的处理，filter会带来其他颜色的影响
                var cList = bgColor.split(',');
                this.dom.style.filter = 'alpha(opacity=' + cList[3].substring(0, cList[3].lastIndexOf(')')) * 100 + ')';
                cList.length = 3;
                cList[0] = cList[0].replace('a', '');
                this.dom.style.backgroundColor = cList.join(',') + ')';
            } else {
                this.dom.style.backgroundColor = bgColor;
            }
        }
        this._zr.clearAnimation();
        this._chartList = [];
        var chartLibrary = require('./chart');
        var componentLibrary = require('./component');
        if (magicOption.xAxis || magicOption.yAxis) {
            magicOption.grid = magicOption.grid || {};
            magicOption.dataZoom = magicOption.dataZoom || {};
        }
        var componentList = [
            'title',
            'legend',
            'tooltip',
            'dataRange',
            'roamController',
            'grid',
            'dataZoom',
            'xAxis',
            'yAxis',
            'polar'
        ];
        var ComponentClass;
        var componentType;
        var component;
        for (var i = 0, l = componentList.length; i < l; i++) {
            componentType = componentList[i];
            component = this.component[componentType];
            if (magicOption[componentType]) {
                if (component) {
                    component.refresh && component.refresh(magicOption);
                } else {
                    ComponentClass = componentLibrary.get(/^[xy]Axis$/.test(componentType) ? 'axis' : componentType);
                    component = new ComponentClass(this._themeConfig, this._messageCenter, this._zr, magicOption, this, componentType);
                    this.component[componentType] = component;
                }
                this._chartList.push(component);
            } else if (component) {
                component.dispose();
                this.component[componentType] = null;
                delete this.component[componentType];
            }
        }
        var ChartClass;
        var chartType;
        var chart;
        var chartMap = {};
        // 记录已经初始化的图表
        for (var i = 0, l = magicOption.series.length; i < l; i++) {
            chartType = magicOption.series[i].type;
            if (!chartType) {
                console.error('series[' + i + '] chart type has not been defined.');
                continue;
            }
            if (!chartMap[chartType]) {
                chartMap[chartType] = true;
                ChartClass = chartLibrary.get(chartType);
                if (ChartClass) {
                    if (this.chart[chartType]) {
                        chart = this.chart[chartType];
                        chart.refresh(magicOption);
                    } else {
                        chart = new ChartClass(this._themeConfig, this._messageCenter, this._zr, magicOption, this);
                    }
                    this._chartList.push(chart);
                    this.chart[chartType] = chart;
                } else {
                    console.error(chartType + ' has not been required.');
                }
            }
        }
        // 已有实例但新option不带这类图表的实例释放
        for (chartType in this.chart) {
            if (chartType != ecConfig.CHART_TYPE_ISLAND && !chartMap[chartType]) {
                this.chart[chartType].dispose();
                this.chart[chartType] = null;
                delete this.chart[chartType];
            }
        }
        this.component.grid && this.component.grid.refixAxisShape(this.component);
        this._island.refresh(magicOption);
        this._toolbox.refresh(magicOption);
        magicOption.animation && !magicOption.renderAsImage ? this._zr.refresh() : this._zr.render();
        var imgId = 'IMG' + this.id;
        var img = document.getElementById(imgId);
        if (magicOption.renderAsImage && _canvasSupported) {
            // IE8- 不支持图片渲染形式
            if (img) {
                // 已经渲染过则更新显示
                img.src = this.getDataURL(magicOption.renderAsImage);
            } else {
                // 没有渲染过插入img dom
                img = this.getImage(magicOption.renderAsImage);
                img.id = imgId;
                img.style.position = 'absolute';
                img.style.left = 0;
                img.style.top = 0;
                this.dom.firstChild.appendChild(img);
            }
            this.un();
            this._zr.un();
            this._disposeChartList();
            this._zr.clear();
        } else if (img) {
            // 删除可能存在的img
            img.parentNode.removeChild(img);
        }
        img = null;
        this._option = magicOption;
    },
    /**
         * 还原
         */
    restore: function () {
        this._clearEffect();
        this._option = zrUtil.clone(this._optionRestore);
        this._disposeChartList();
        this._island.clear();
        this._toolbox.reset(this._option, true);
        this._render(this._option);
    },
    /**
         * 刷新
         * @param {Object=} param，可选参数，用于附带option，内部同步用，外部不建议带入数据修改，无法同步
         */
    refresh: function (param) {
        this._clearEffect();
        param = param || {};
        var magicOption = param.option;
        // 外部调用的refresh且有option带入
        if (!this._refreshInside && magicOption) {
            // 做简单的差异合并去同步内部持有的数据克隆，不建议带入数据
            // 开启数据区域缩放、拖拽重计算、数据视图可编辑模式情况下，当用户产生了数据变化后无法同步
            // 如有带入option存在数据变化，请重新setOption
            magicOption = this.getOption();
            zrUtil.merge(magicOption, param.option, true);
            zrUtil.merge(this._optionRestore, param.option, true);
            this._toolbox.reset(magicOption);
        }
        this._island.refresh(magicOption);
        this._toolbox.refresh(magicOption);
        // 停止动画
        this._zr.clearAnimation();
        // 先来后到，安顺序刷新各种图表，图表内部refresh优化检查magicOption，无需更新则不更新~
        for (var i = 0, l = this._chartList.length; i < l; i++) {
            this._chartList[i].refresh && this._chartList[i].refresh(magicOption);
        }
        this.component.grid && this.component.grid.refixAxisShape(this.component);
        this._zr.refresh();
    },
    /**
         * 释放图表实例
         */
    _disposeChartList: function () {
        this._clearEffect();
        // 停止动画
        this._zr.clearAnimation();
        var len = this._chartList.length;
        while (len--) {
            var chart = this._chartList[len];
            if (chart) {
                var chartType = chart.type;
                this.chart[chartType] && delete this.chart[chartType];
                this.component[chartType] && delete this.component[chartType];
                chart.dispose && chart.dispose();
            }
        }
        this._chartList = [];
    },
    /**
         * 非图表全局属性merge~~
         */
    _mergeGlobalConifg: function (magicOption) {
        var mergeList = [
            // 背景颜色
            'backgroundColor',
            // 拖拽重计算相关
            'calculable',
            'calculableColor',
            'calculableHolderColor',
            // 孤岛显示连接符
            'nameConnector',
            'valueConnector',
            // 动画相关
            'animation',
            'animationThreshold',
            'animationDuration',
            'animationDurationUpdate',
            'animationEasing',
            'addDataAnimation',
            // 默认标志图形类型列表
            'symbolList',
            // 降低图表内元素拖拽敏感度，单位ms，不建议外部干预
            'DRAG_ENABLE_TIME'
        ];
        var len = mergeList.length;
        while (len--) {
            var mergeItem = mergeList[len];
            if (magicOption[mergeItem] == null) {
                magicOption[mergeItem] = this._themeConfig[mergeItem] != null ? this._themeConfig[mergeItem] : ecConfig[mergeItem];
            }
        }
        // 数值系列的颜色列表，不传则采用内置颜色，可配数组，借用zrender实例注入，会有冲突风险，先这样
        var themeColor = magicOption.color;
        if (!(themeColor && themeColor.length)) {
            themeColor = this._themeConfig.color || ecConfig.color;
        }
        this._zr.getColor = function (idx) {
            var zrColor = require('zrender/tool/color');
            return zrColor.getColor(idx, themeColor);
        };
        if (!_canvasSupported) {
            // 不支持Canvas的强制关闭动画
            magicOption.animation = false;
            magicOption.addDataAnimation = false;
        }
    },
    /**
         * 万能接口，配置图表实例任何可配置选项，多次调用时option选项做merge处理
         * @param {Object} option
         * @param {boolean=} notMerge 多次调用时option选项是默认是合并（merge）的，
         *                   如果不需求，可以通过notMerger参数为true阻止与上次option的合并
         */
    setOption: function (option, notMerge) {
        if (!option.timeline) {
            return this._setOption(option, notMerge);
        } else {
            return this._setTimelineOption(option);
        }
    },
    /**
         * 万能接口，配置图表实例任何可配置选项，多次调用时option选项做merge处理
         * @param {Object} option
         * @param {boolean=} notMerge 多次调用时option选项是默认是合并（merge）的，
         *                   如果不需求，可以通过notMerger参数为true阻止与上次option的合并
         * @param {boolean=} 默认false。keepTimeLine 表示从timeline组件调用而来，
         *                   表示当前行为是timeline的数据切换，保持timeline，
         *                   反之销毁timeline。 详见Issue #1601
         */
    _setOption: function (option, notMerge, keepTimeLine) {
        if (!notMerge && this._option) {
            this._option = zrUtil.merge(this.getOption(), zrUtil.clone(option), true);
        } else {
            this._option = zrUtil.clone(option);
            !keepTimeLine && this._timeline && this._timeline.dispose();
        }
        this._optionRestore = zrUtil.clone(this._option);
        if (!this._option.series || this._option.series.length === 0) {
            this._zr.clear();
            return;
        }
        if (this.component.dataZoom && (this._option.dataZoom || this._option.toolbox && this._option.toolbox.feature && this._option.toolbox.feature.dataZoom && this._option.toolbox.feature.dataZoom.show)) {
            // dataZoom同步数据
            this.component.dataZoom.syncOption(this._option);
        }
        this._toolbox.reset(this._option);
        this._render(this._option);
        return this;
    },
    /**
         * 返回内部持有的当前显示option克隆
         */
    getOption: function () {
        var magicOption = zrUtil.clone(this._option);
        var self = this;
        function restoreOption(prop) {
            var restoreSource = self._optionRestore[prop];
            if (restoreSource) {
                if (restoreSource instanceof Array) {
                    var len = restoreSource.length;
                    while (len--) {
                        magicOption[prop][len].data = zrUtil.clone(restoreSource[len].data);
                    }
                } else {
                    magicOption[prop].data = zrUtil.clone(restoreSource.data);
                }
            }
        }
        // 横轴数据还原
        restoreOption('xAxis');
        // 纵轴数据还原
        restoreOption('yAxis');
        // 系列数据还原
        restoreOption('series');
        return magicOption;
    },
    /**
         * 数据设置快捷接口
         * @param {Array} series
         * @param {boolean=} notMerge 多次调用时option选项是默认是合并（merge）的，
         *                   如果不需求，可以通过notMerger参数为true阻止与上次option的合并。
         */
    setSeries: function (series, notMerge) {
        if (!notMerge) {
            this.setOption({ series: series });
        } else {
            this._option.series = series;
            this.setOption(this._option, notMerge);
        }
        return this;
    },
    /**
         * 返回内部持有的当前显示series克隆
         */
    getSeries: function () {
        return this.getOption().series;
    },
    /**
         * timelineOption接口，配置图表实例任何可配置选项
         * @param {Object} option
         */
    _setTimelineOption: function (option) {
        this._timeline && this._timeline.dispose();
        var Timeline = require('./component/timeline');
        var timeline = new Timeline(this._themeConfig, this._messageCenter, this._zr, option, this);
        this._timeline = timeline;
        this.component.timeline = this._timeline;
        return this;
    },
    /**
         * 动态数据添加
         * 形参为单组数据参数，多组时为数据，内容同[seriesIdx, data, isShift, additionData]
         * @param {number} seriesIdx 系列索引
         * @param {number | Object} data 增加数据
         * @param {boolean=} isHead 是否队头加入，默认，不指定或false时为队尾插入
         * @param {boolean=} dataGrow 是否增长数据队列长度，默认，不指定或false时移出目标数组对位数据
         * @param {string=} additionData 是否增加类目轴(饼图为图例)数据，附加操作同isHead和dataGrow
         */
    addData: function (seriesIdx, data, isHead, dataGrow, additionData) {
        var params = seriesIdx instanceof Array ? seriesIdx : [[
                seriesIdx,
                data,
                isHead,
                dataGrow,
                additionData
            ]];
        //this._optionRestore 和 magicOption 都要同步
        var magicOption = this.getOption();
        var optionRestore = this._optionRestore;
        var self = this;
        for (var i = 0, l = params.length; i < l; i++) {
            seriesIdx = params[i][0];
            data = params[i][1];
            isHead = params[i][2];
            dataGrow = params[i][3];
            additionData = params[i][4];
            var seriesItem = optionRestore.series[seriesIdx];
            var inMethod = isHead ? 'unshift' : 'push';
            var outMethod = isHead ? 'pop' : 'shift';
            if (seriesItem) {
                var seriesItemData = seriesItem.data;
                var mSeriesItemData = magicOption.series[seriesIdx].data;
                seriesItemData[inMethod](data);
                mSeriesItemData[inMethod](data);
                if (!dataGrow) {
                    seriesItemData[outMethod]();
                    data = mSeriesItemData[outMethod]();
                }
                if (additionData != null) {
                    var legend;
                    var legendData;
                    if (seriesItem.type === ecConfig.CHART_TYPE_PIE && (legend = optionRestore.legend) && (legendData = legend.data)) {
                        var mLegendData = magicOption.legend.data;
                        legendData[inMethod](additionData);
                        mLegendData[inMethod](additionData);
                        if (!dataGrow) {
                            var legendDataIdx = zrUtil.indexOf(legendData, data.name);
                            legendDataIdx != -1 && legendData.splice(legendDataIdx, 1);
                            legendDataIdx = zrUtil.indexOf(mLegendData, data.name);
                            legendDataIdx != -1 && mLegendData.splice(legendDataIdx, 1);
                        }
                    } else if (optionRestore.xAxis != null && optionRestore.yAxis != null) {
                        // x轴类目
                        var axisData;
                        var mAxisData;
                        var axisIdx = seriesItem.xAxisIndex || 0;
                        if (optionRestore.xAxis[axisIdx].type == null || optionRestore.xAxis[axisIdx].type === 'category') {
                            axisData = optionRestore.xAxis[axisIdx].data;
                            mAxisData = magicOption.xAxis[axisIdx].data;
                            axisData[inMethod](additionData);
                            mAxisData[inMethod](additionData);
                            if (!dataGrow) {
                                axisData[outMethod]();
                                mAxisData[outMethod]();
                            }
                        }
                        // y轴类目
                        axisIdx = seriesItem.yAxisIndex || 0;
                        if (optionRestore.yAxis[axisIdx].type === 'category') {
                            axisData = optionRestore.yAxis[axisIdx].data;
                            mAxisData = magicOption.yAxis[axisIdx].data;
                            axisData[inMethod](additionData);
                            mAxisData[inMethod](additionData);
                            if (!dataGrow) {
                                axisData[outMethod]();
                                mAxisData[outMethod]();
                            }
                        }
                    }
                }
                // 同步图表内状态，动画需要
                this._option.series[seriesIdx].data = magicOption.series[seriesIdx].data;
            }
        }
        this._zr.clearAnimation();
        var chartList = this._chartList;
        var chartAnimationCount = 0;
        var chartAnimationDone = function () {
            chartAnimationCount--;
            if (chartAnimationCount === 0) {
                animationDone();
            }
        };
        for (var i = 0, l = chartList.length; i < l; i++) {
            if (magicOption.addDataAnimation && chartList[i].addDataAnimation) {
                chartAnimationCount++;
                chartList[i].addDataAnimation(params, chartAnimationDone);
            }
        }
        // dataZoom同步数据
        this.component.dataZoom && this.component.dataZoom.syncOption(magicOption);
        this._option = magicOption;
        function animationDone() {
            if (!self._zr) {
                return;    // 已经被释放
            }
            self._zr.clearAnimation();
            for (var i = 0, l = chartList.length; i < l; i++) {
                // 有addData动画就去掉过渡动画
                chartList[i].motionlessOnce = magicOption.addDataAnimation && chartList[i].addDataAnimation;
            }
            self._messageCenter.dispatch(ecConfig.EVENT.REFRESH, null, { option: magicOption }, self);
        }
        if (!magicOption.addDataAnimation) {
            setTimeout(animationDone, 0);
        }
        return this;
    },
    /**
         * 动态[标注 | 标线]添加
         * @param {number} seriesIdx 系列索引
         * @param {Object} markData [标注 | 标线]对象，支持多个
         */
    addMarkPoint: function (seriesIdx, markData) {
        return this._addMark(seriesIdx, markData, 'markPoint');
    },
    addMarkLine: function (seriesIdx, markData) {
        return this._addMark(seriesIdx, markData, 'markLine');
    },
    _addMark: function (seriesIdx, markData, markType) {
        var series = this._option.series;
        var seriesItem;
        if (series && (seriesItem = series[seriesIdx])) {
            var seriesR = this._optionRestore.series;
            var seriesRItem = seriesR[seriesIdx];
            var markOpt = seriesItem[markType];
            var markOptR = seriesRItem[markType];
            markOpt = seriesItem[markType] = markOpt || { data: [] };
            markOptR = seriesRItem[markType] = markOptR || { data: [] };
            for (var key in markData) {
                if (key === 'data') {
                    // 数据concat
                    markOpt.data = markOpt.data.concat(markData.data);
                    markOptR.data = markOptR.data.concat(markData.data);
                } else if (typeof markData[key] != 'object' || markOpt[key] == null) {
                    // 简单类型或新值直接赋值
                    markOpt[key] = markOptR[key] = markData[key];
                } else {
                    // 非数据的复杂对象merge
                    zrUtil.merge(markOpt[key], markData[key], true);
                    zrUtil.merge(markOptR[key], markData[key], true);
                }
            }
            var chart = this.chart[seriesItem.type];
            chart && chart.addMark(seriesIdx, markData, markType);
        }
        return this;
    },
    /**
         * 动态[标注 | 标线]删除
         * @param {number} seriesIdx 系列索引
         * @param {string} markName [标注 | 标线]名称
         */
    delMarkPoint: function (seriesIdx, markName) {
        return this._delMark(seriesIdx, markName, 'markPoint');
    },
    delMarkLine: function (seriesIdx, markName) {
        return this._delMark(seriesIdx, markName, 'markLine');
    },
    _delMark: function (seriesIdx, markName, markType) {
        var series = this._option.series;
        var seriesItem;
        var mark;
        var dataArray;
        if (!(series && (seriesItem = series[seriesIdx]) && (mark = seriesItem[markType]) && (dataArray = mark.data))) {
            return this;
        }
        markName = markName.split(' > ');
        var targetIndex = -1;
        for (var i = 0, l = dataArray.length; i < l; i++) {
            var dataItem = dataArray[i];
            if (dataItem instanceof Array) {
                if (dataItem[0].name === markName[0] && dataItem[1].name === markName[1]) {
                    targetIndex = i;
                    break;
                }
            } else if (dataItem.name === markName[0]) {
                targetIndex = i;
                break;
            }
        }
        if (targetIndex > -1) {
            dataArray.splice(targetIndex, 1);
            this._optionRestore.series[seriesIdx][markType].data.splice(targetIndex, 1);
            var chart = this.chart[seriesItem.type];
            chart && chart.delMark(seriesIdx, markName.join(' > '), markType);
        }
        return this;
    },
    /**
         * 获取当前dom
         */
    getDom: function () {
        return this.dom;
    },
    /**
         * 获取当前zrender实例，可用于添加额为的shape和深度控制
         */
    getZrender: function () {
        return this._zr;
    },
    /**
         * 获取Base64图片dataURL
         * @param {string} imgType 图片类型，支持png|jpeg，默认为png
         * @return imgDataURL
         */
    getDataURL: function (imgType) {
        if (!_canvasSupported) {
            return '';
        }
        if (this._chartList.length === 0) {
            // 渲染为图片
            var imgId = 'IMG' + this.id;
            var img = document.getElementById(imgId);
            if (img) {
                return img.src;
            }
        }
        // 清除可能存在的tooltip元素
        var tooltip = this.component.tooltip;
        tooltip && tooltip.hideTip();
        switch (imgType) {
        case 'jpeg':
            break;
        default:
            imgType = 'png';
        }
        var bgColor = this._option.backgroundColor;
        if (bgColor && bgColor.replace(' ', '') === 'rgba(0,0,0,0)') {
            bgColor = '#fff';
        }
        return this._zr.toDataURL('image/' + imgType, bgColor);
    },
    /**
         * 获取img
         * @param {string} imgType 图片类型，支持png|jpeg，默认为png
         * @return img dom
         */
    getImage: function (imgType) {
        var title = this._optionRestore.title;
        var imgDom = document.createElement('img');
        imgDom.src = this.getDataURL(imgType);
        imgDom.title = title && title.text || 'ECharts';
        return imgDom;
    },
    /**
         * 获取多图联动的Base64图片dataURL
         * @param {string} imgType 图片类型，支持png|jpeg，默认为png
         * @return imgDataURL
         */
    getConnectedDataURL: function (imgType) {
        if (!this.isConnected()) {
            return this.getDataURL(imgType);
        }
        var tempDom = this.dom;
        var imgList = {
            'self': {
                img: this.getDataURL(imgType),
                left: tempDom.offsetLeft,
                top: tempDom.offsetTop,
                right: tempDom.offsetLeft + tempDom.offsetWidth,
                bottom: tempDom.offsetTop + tempDom.offsetHeight
            }
        };
        var minLeft = imgList.self.left;
        var minTop = imgList.self.top;
        var maxRight = imgList.self.right;
        var maxBottom = imgList.self.bottom;
        for (var c in this._connected) {
            tempDom = this._connected[c].getDom();
            imgList[c] = {
                img: this._connected[c].getDataURL(imgType),
                left: tempDom.offsetLeft,
                top: tempDom.offsetTop,
                right: tempDom.offsetLeft + tempDom.offsetWidth,
                bottom: tempDom.offsetTop + tempDom.offsetHeight
            };
            minLeft = Math.min(minLeft, imgList[c].left);
            minTop = Math.min(minTop, imgList[c].top);
            maxRight = Math.max(maxRight, imgList[c].right);
            maxBottom = Math.max(maxBottom, imgList[c].bottom);
        }
        var zrDom = document.createElement('div');
        zrDom.style.position = 'absolute';
        zrDom.style.left = '-4000px';
        zrDom.style.width = maxRight - minLeft + 'px';
        zrDom.style.height = maxBottom - minTop + 'px';
        document.body.appendChild(zrDom);
        var zrImg = require('zrender').init(zrDom);
        var ImageShape = require('zrender/shape/Image');
        for (var c in imgList) {
            zrImg.addShape(new ImageShape({
                style: {
                    x: imgList[c].left - minLeft,
                    y: imgList[c].top - minTop,
                    image: imgList[c].img
                }
            }));
        }
        zrImg.render();
        var bgColor = this._option.backgroundColor;
        if (bgColor && bgColor.replace(/ /g, '') === 'rgba(0,0,0,0)') {
            bgColor = '#fff';
        }
        var image = zrImg.toDataURL('image/png', bgColor);
        setTimeout(function () {
            zrImg.dispose();
            zrDom.parentNode.removeChild(zrDom);
            zrDom = null;
        }, 100);
        return image;
    },
    /**
         * 获取多图联动的img
         * @param {string} imgType 图片类型，支持png|jpeg，默认为png
         * @return img dom
         */
    getConnectedImage: function (imgType) {
        var title = this._optionRestore.title;
        var imgDom = document.createElement('img');
        imgDom.src = this.getConnectedDataURL(imgType);
        imgDom.title = title && title.text || 'ECharts';
        return imgDom;
    },
    /**
         * 外部接口绑定事件
         * @param {Object} eventName 事件名称
         * @param {Object} eventListener 事件响应函数
         */
    on: function (eventName, eventListener) {
        this._messageCenterOutSide.bind(eventName, eventListener, this);
        return this;
    },
    /**
         * 外部接口解除事件绑定
         * @param {Object} eventName 事件名称
         * @param {Object} eventListener 事件响应函数
         */
    un: function (eventName, eventListener) {
        this._messageCenterOutSide.unbind(eventName, eventListener);
        return this;
    },
    /**
         * 多图联动
         * @param connectTarget{ECharts | Array <ECharts>} connectTarget 联动目标
         */
    connect: function (connectTarget) {
        if (!connectTarget) {
            return this;
        }
        if (!this._connected) {
            this._connected = {};
        }
        if (connectTarget instanceof Array) {
            for (var i = 0, l = connectTarget.length; i < l; i++) {
                this._connected[connectTarget[i].id] = connectTarget[i];
            }
        } else {
            this._connected[connectTarget.id] = connectTarget;
        }
        return this;
    },
    /**
         * 解除多图联动
         * @param connectTarget{ECharts | Array <ECharts>} connectTarget 解除联动目标
         */
    disConnect: function (connectTarget) {
        if (!connectTarget || !this._connected) {
            return this;
        }
        if (connectTarget instanceof Array) {
            for (var i = 0, l = connectTarget.length; i < l; i++) {
                delete this._connected[connectTarget[i].id];
            }
        } else {
            delete this._connected[connectTarget.id];
        }
        for (var k in this._connected) {
            return k, this;    // 非空
        }
        // 空，转为标志位
        this._connected = false;
        return this;
    },
    /**
         * 联动事件响应
         */
    connectedEventHandler: function (param) {
        if (param.__echartsId != this.id) {
            // 来自其他联动图表的事件
            this._onevent(param);
        }
    },
    /**
         * 是否存在多图联动
         */
    isConnected: function () {
        return !!this._connected;
    },
    /**
         * 显示loading过渡
         * @param {Object} loadingOption
         */
    showLoading: function (loadingOption) {
        var effectList = {
            bar: require('zrender/loadingEffect/Bar'),
            bubble: require('zrender/loadingEffect/Bubble'),
            dynamicLine: require('zrender/loadingEffect/DynamicLine'),
            ring: require('zrender/loadingEffect/Ring'),
            spin: require('zrender/loadingEffect/Spin'),
            whirling: require('zrender/loadingEffect/Whirling')
        };
        this._toolbox.hideDataView();
        loadingOption = loadingOption || {};
        var textStyle = loadingOption.textStyle || {};
        loadingOption.textStyle = textStyle;
        var finalTextStyle = zrUtil.merge(zrUtil.merge(zrUtil.clone(textStyle), this._themeConfig.textStyle), ecConfig.textStyle);
        textStyle.textFont = finalTextStyle.fontStyle + ' ' + finalTextStyle.fontWeight + ' ' + finalTextStyle.fontSize + 'px ' + finalTextStyle.fontFamily;
        textStyle.text = loadingOption.text || this._option && this._option.loadingText || this._themeConfig.loadingText || ecConfig.loadingText;
        if (loadingOption.x != null) {
            textStyle.x = loadingOption.x;
        }
        if (loadingOption.y != null) {
            textStyle.y = loadingOption.y;
        }
        loadingOption.effectOption = loadingOption.effectOption || {};
        loadingOption.effectOption.textStyle = textStyle;
        var Effect = loadingOption.effect;
        if (typeof Effect === 'string' || Effect == null) {
            Effect = effectList[loadingOption.effect || this._option && this._option.loadingEffect || this._themeConfig.loadingEffect || ecConfig.loadingEffect] || effectList.spin;
        }
        this._zr.showLoading(new Effect(loadingOption.effectOption));
        return this;
    },
    /**
         * 隐藏loading过渡
         */
    hideLoading: function () {
        this._zr.hideLoading();
        return this;
    },
    /**
         * 主题设置
         */
    setTheme: function (theme) {
        if (theme) {
            if (typeof theme === 'string') {
                // 默认主题
                switch (theme) {
                case 'macarons':
                    theme = require('./theme/macarons');
                    break;
                case 'infographic':
                    theme = require('./theme/infographic');
                    break;
                default:
                    theme = {};    // require('./theme/default');
                }
            } else {
                theme = theme || {};
            }
            // // 复位默认配置
            // // this._themeConfig会被别的对象引用持有
            // // 所以不能改成this._themeConfig = {};
            // for (var key in this._themeConfig) {
            //     delete this._themeConfig[key];
            // }
            // for (var key in ecConfig) {
            //     this._themeConfig[key] = zrUtil.clone(ecConfig[key]);
            // }
            // // 颜色数组随theme，不merge
            // theme.color && (this._themeConfig.color = []);
            // // 默认标志图形类型列表，不merge
            // theme.symbolList && (this._themeConfig.symbolList = []);
            // // 应用新主题
            // zrUtil.merge(this._themeConfig, zrUtil.clone(theme), true);
            this._themeConfig = theme;
        }
        if (!_canvasSupported) {
            // IE8-
            var textStyle = this._themeConfig.textStyle;
            textStyle && textStyle.fontFamily && textStyle.fontFamily2 && (textStyle.fontFamily = textStyle.fontFamily2);
            textStyle = ecConfig.textStyle;
            textStyle.fontFamily = textStyle.fontFamily2;
        }
        this._timeline && this._timeline.setTheme(true);
        this._optionRestore && this.restore();
    },
    /**
         * 视图区域大小变化更新，不默认绑定，供使用方按需调用
         */
    resize: function () {
        var self = this;
        return function () {
            self._clearEffect();
            self._zr.resize();
            if (self._option && self._option.renderAsImage && _canvasSupported) {
                // 渲染为图片重走render模式
                self._render(self._option);
                return self;
            }
            // 停止动画
            self._zr.clearAnimation();
            self._island.resize();
            self._toolbox.resize();
            self._timeline && self._timeline.resize();
            // 先来后到，不能仅刷新自己，也不能在上一个循环中刷新，如坐标系数据改变会影响其他图表的大小
            // 所以安顺序刷新各种图表，图表内部refresh优化无需更新则不更新~
            for (var i = 0, l = self._chartList.length; i < l; i++) {
                self._chartList[i].resize && self._chartList[i].resize();
            }
            self.component.grid && self.component.grid.refixAxisShape(self.component);
            self._zr.refresh();
            self._messageCenter.dispatch(ecConfig.EVENT.RESIZE, null, null, self);
            return self;
        };
    },
    _clearEffect: function () {
        this._zr.modLayer(ecConfig.EFFECT_ZLEVEL, { motionBlur: false });
        this._zr.painter.clearLayer(ecConfig.EFFECT_ZLEVEL);
    },
    /**
         * 清除已渲染内容 ，clear后echarts实例可用
         */
    clear: function () {
        this._disposeChartList();
        this._zr.clear();
        this._option = {};
        this._optionRestore = {};
        this.dom.style.backgroundColor = null;
        return this;
    },
    /**
         * 释放，dispose后echarts实例不可用
         */
    dispose: function () {
        var key = this.dom.getAttribute(DOM_ATTRIBUTE_KEY);
        key && delete _instances[key];
        this._island.dispose();
        this._toolbox.dispose();
        this._timeline && this._timeline.dispose();
        this._messageCenter.unbind();
        this.clear();
        this._zr.dispose();
        this._zr = null;
    }
};
module.exports = self || module.exports;;